package test.generic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 泛型通配符
 */
/**
 * (1) PECS原则（Producer Extends Consumer Super）
 * 如果要从集合中读取类型T的数据，并且不能写入，可以使用 ? extends 通配符 (Producer Extends)
 * 如果要从集合中写入类型T的数据，并且不需要读取，可以使用 ? super 通配符 (Consumer Super)
 */
/**
 * (2) 无边界的通配符 <?>:
 * 作用就是让泛型能够接受未知类型的数据
 *
 * 注意, List<?>不等同于List<Object>,
 * 因为虽然Object类是所有类的父类, 但是List<Object>跟其他泛型的List如List<String>, List<Integer>不存在继承关系, 因此会报错
 *
 * 所以重要的事情说三遍：
 *
 * 就算容器里装的东西之间有继承关系，但装这些东西的容器之间是没有继承关系的
 *
 * 就算容器里装的东西之间有继承关系，但装这些东西的容器之间是没有继承关系的
 *
 * 就算容器里装的东西之间有继承关系，但装这些东西的容器之间是没有继承关系的
 *
 * */
public class GenericWildcard {


    public static void acceptEveryType(List<?> list) {

        for (Object o : list) {
            System.out.println(o);
        }
        /**
         * 有一点我们必须明确, 我们不能对List<?>使用add方法, 仅有一个例外, 就是add(null)，但是在实际运行的时候也会报错.
         * 为什么呢? 因为我们不确定该List的类型, 不知道add什么类型的数据才对, 只有null是所有引用数据类型都具有的元素
         */

        //list.add("AA"); // 编译报错
        //list.add(new Object()); // 编译报错
        //list.add(1); // 编译报错

        /**
         * 还有, List<?>也不能使用get方法, 只有Object类型是个例外.
         * 原因也很简单, 因为我们不知道传入的List是什么泛型的, 所以无法接受得到的get, 但是Object是所有数据类型的父类, 所以只有接受他可以
         */
        // String s = list.get(0); // 编译报错
        // Integer i = list.get(0); // 编译报错
        Object o = list.get(0);
    }

    /**
     * <? extends E> 指定上限的通配符：能够接受指定类及其子类类型的数据
     *
     * @param list
     */
    public static double testExtendsNumber(List<? extends Number> list) {

        /**
         * List<? extends E>不能使用add方法
         */
        // list.add(1); // 编译报错
        // list.add(1.1); // 编译报错

        /**
         * 可以用get方法来获取E
         */
        // Integer i = list.get(0); //编译报错
        // Double d = list.get(0); //编译报错
        Number n = list.get(0);

        double s = 0.0;
        for (Number l : list) {
            // 注意这里得到的n是其上边界类型的, 也就是Number, 需要将其转换为double.
            s += l.doubleValue();
        }
        return s;
    }

    public static void testNumber(List<Number> list) {

    }

    /**
     * <? super E> 指定下限的通配符：能够接受指定类及其父类类型的数据
     *
     * @param list
     */
    public static void testSuperInteger(List<? super Integer> list) {

        /**
         * List<? super E>能够调用add方法的,
         */

        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }

        /**
         * 但是不能用get方法来获取E，除了Object为特例, 因为所有类型都是其子类型
         */
        //Integer i = list.get(0); //编译报错
        Object o = list.get(0);
    }

    public static void main(String[] args) {

        //可以将任何类型的List赋值给List<?>
        List<String> l1 = new ArrayList<>();
        acceptEveryType(l1);

        List<Integer> l2 = new ArrayList<>();
        acceptEveryType(l2);

        List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
        testExtendsNumber(list1);

        List<Double> list2 = Arrays.asList(1.1, 2.2, 3.3, 4.4);
        testExtendsNumber(list2);

        List<Object> list3 = new ArrayList<>();
        testSuperInteger(list3);

        List<Number> list4 = new ArrayList<>();
        testSuperInteger(list4);

        List<Double> list5 = new ArrayList<>();
        // testSuperInteger(list5); // 编译报错

        /**
         * 数组的协变:
         *  因为Integer是Number的子类，一个Integer对象也是一个Number对象，所以一个Integer的数组也是一个Number的数组，这就是数组的协变
         *
         *  Java把数组设计成协变的，在一定程度上是有缺陷的。因为尽管把Integer[]赋值给Number[]，Integer[]可以向上转型为Number[]，
         *  但是数据元素的实际类型是Integer，只能向数组中放入Integer或者Integer的子类。
         *
         *  如果向数组中放入Number对象或者Number其他子类的对象，对于编译器来说也是可以通过编译的。
         *  但是运行时JVM能够知道数组元素的实际类型是Integer，当其它对象加入数组是就会抛出异常（java.lang.ArrayStoreException）
         *
         */
        Number[] nums = new Integer[10]; // OK
        nums[0] = new Double(0); // 运行时会抛出异常 java.lang.ArrayStoreException: java.lang.Double

    }
}